/* ============================================================
 * Part of 'Autostop' project
 * Under GPL license - http://www.gnu.org/licenses/gpl.html
 * (c) 2006 - Autostop team
 */

///@file guidriverwindow.cpp Declaration of gui driver's main window

#include <QDir>
#include <QSettings>
#include <QLabel>
#include <QStatusBar>
#include <QFileDialog>
#include <QSlider>
#include <QApplication>
#include <QPainter>

#include "guidriverwindow.h"

// static members
QVector<QRgb> GuiDriverWindow::_grayscale;
QVector<QColor> GuiDriverWindow::_basicColors;


// ============================================================
/// Constructor
GuiDriverWindow::GuiDriverWindow() : QMainWindow()
{
	// init gui
	setupUi( this );
	progressBar->hide();
	
	// init memebrs
	restorePath();
	_pmarkers = NULL;
}

// ============================================================
/// Start button click handler
void GuiDriverWindow::on_startButton_clicked()
{
	qDebug("loading sequence");
	loadSequence( _path );
	processSequence();
}

// ============================================================
/// Loads sequence from path into _sequence
void GuiDriverWindow::loadSequence( const QString& path )
{
	// get dir
	QDir dir( path );
	
	if ( ! dir.exists() )
	{
		statusBar()->showMessage(QString(tr("path %1 does not exists!")).arg(dir.absolutePath()) );
	}

	// clear sequence
	_sequence.clear();

	// get list of png files
	QStringList nameFilters;
	nameFilters << "*.png" << "*.PNG";
	
	QStringList files = dir.entryList( nameFilters, QDir::Files | QDir::Readable, QDir::Name );
	qDebug("loadSequence: %d files", files.size() );

	startLengthyProcess( tr("Loading sequence files"), files.size() );

	// preallocate vectors
	_sequence.reserve( files.size() );
	
	// populate sequence
	QString file;
	int i = 1;
	foreach( file, files ){
		QFileInfo fileInfo( dir, file );
		QImage image( fileInfo.absoluteFilePath(), "PNG" );

		if ( image.isNull() ) qDebug("could not load %s", (const char*)fileInfo.absoluteFilePath().toAscii() );
		
		_sequence.append( image );
		indicateIteration( i++ ); 
	}

	// load distance markers
	loadMarkers( path );
	
	endLengthyProcess();
	statusBar()->showMessage(QString(tr("%1 files loaded")).arg( files.size() ) );
	storePath();

	// update slider
	if ( _sequence.size() ) {
		positionSlider->setMinimum( 1 );
		positionSlider->setMaximum( _sequence.size() );
		positionSlider->setValue( 1 );

		positionSlider->setEnabled( true );
	} else {
		positionSlider->setEnabled( false );
	}

	on_positionSlider_sliderMoved( 1 );
}

// ============================================================
/// Loads distance msrkers
///\par	path	directory path where the 'markers.image' file is stored
void GuiDriverWindow::loadMarkers( const QString& path )
{
	// get dir
	QDir dir( path );
	
	QFileInfo distanceMapInfo( dir, "markers.image" );
	free_image( _pmarkers );
	const char* filepath = (const char*)distanceMapInfo.absoluteFilePath().toAscii();
	FILE* hmarkers = fopen( filepath , "r" );

	if ( hmarkers )
	{
		_pmarkers = load_image( hmarkers );
		fclose( hmarkers );
		if ( !_pmarkers ){
			perror("Could not load marker");
		}
	}
	else
	{
		perror("could not open markers.image");
	}

	_markersImage = markerToQtImage( _pmarkers );
}

// ============================================================
/// converts marker image into its visual representation
QImage GuiDriverWindow::markerToQtImage( const pimage_t pmarker )
{
	if ( pmarker )
	{
		QImage image( pmarker->width, pmarker->height, QImage::Format_ARGB32 );
		image.fill( 0 );

		for( int y = 0; y< pmarker->height; y++) {
			for( int x = 0; x< pmarker->width; x++ ) {
				if ( x > 0 && y > 0 && image_pixel( pmarker, x, y ) != image_pixel( pmarker, x-1, y-1 ) )
				{
					image.setPixel( x, y, qRgb( 255,255,255 ) );
				}
			}
		}
		return image;
	}

	return QImage(); // null image
}

// ============================================================
/// Stores _path in persistent storage (QSettings)
void GuiDriverWindow::storePath()
{
	QSettings settings("autostop", "guidriver");

	settings.setValue( "sequencePath", _path );
}

// ============================================================
/// Loads _path from persistent storage (QSettings)
void GuiDriverWindow::restorePath()
{
	QSettings settings("autostop", "guidriver");

	QString path = settings.value( "sequencePath", "." ).toString();
	setPath( path );
}

// ============================================================
/// Sets sequence path, updates view, stores path in persistend sotrage
void GuiDriverWindow::setPath( const QString& path )
{
	_path = path;
	pathLabel->setText( _path );
	storePath();
}

// ============================================================
/// Opens dir selection window
void GuiDriverWindow::on_selectDirButton_clicked()
{
	QString path = QFileDialog::getExistingDirectory( this, tr("Select sequence directory"),
		_path );

	if ( ! path.isEmpty() ){
		setPath( path );
	}
}

// ============================================================
/// Converts Qt's QImage into analyzer library oimage object, using luminance as value
///@param image	source image
///@return new imae object. must be deleted witf free_image()
pimage_t GuiDriverWindow::imageFromQt( const QImage& image )
{
	// create image
	pimage_t pimage = create_image( image.width(), image.height() );

	// copy data
	assert( pimage );
	
	for( int x = 0; x < pimage->width; x++ ){
		for( int y = 0 ; y < pimage->height; y++ ){
			// 8-bit images
			if ( image.format() == QImage::Format_Indexed8 ){
				image_pixel( pimage, x, y ) = image.pixelIndex( x, y );
			// truecolor images
			} else {
				QRgb rgb = image.pixel( x, y );
				image_pixel( pimage, x, y ) = qGray( rgb );
			}
		}
	}
	
	return pimage;
}

// ============================================================
/// Converts analyzer library image object into Qt's QImage, using image value as luminance
///@param pimage	source image
///@return QImage. note taht resulting image is grayscale
QImage GuiDriverWindow::imageToQt( const pimage_t pimage )
{
	assert( pimage );

	// create image
	QImage image( pimage->width, pimage->height, QImage::Format_Indexed8  );
	// create colormap if needed
	if ( _grayscale.size() == 0 ) {
		_grayscale.resize( 256 );
		for( int i=0; i< 256; i++ ) _grayscale[i] = qRgb( i, i, i);
	}
	image.setColorTable( _grayscale );
	

	// copy data
	for( int x = 0; x < pimage->width; x++ ){
		for( int y = 0 ; y < pimage->height; y++ ){
			int value = image_pixel(pimage, x, y );
			image.setPixel( x, y, value );
		}
	}
	return image;
}

// ============================================================
/// Processes sequence stored in _sequence.
void GuiDriverWindow::processSequence()
{
	if ( _sequence.size() == 0 ) return;

	startLengthyProcess( "Processing data", _sequence.size() );
	
	// first - clear buffers
	_backgrounds.clear();
	_segmented.clear();

	// reserve place for all images
	_backgrounds.reserve( _sequence.size() );
	_segmented.reserve( _sequence.size() );

	// create process
	pprocess_t pprocess = create_process();
	assert( pprocess );

	// supply distance markers
	pprocess->pdistance_markers = _pmarkers;
	pprocess->reference_distance = 40000; // 40 m

	// supply frames	
	QImage image;
	uint64_t timestamp = 0; 
	foreach( image, _sequence ) {
		// create analyzer image from sequence image
		pimage_t pimage = imageFromQt( image );
		// supply image into processor
		add_frame( pprocess, pimage, timestamp );
		// get intermediate images from process data
		_backgrounds.append( imageToQt( pprocess->pbackground ) );
		_segmented.append( paintSegmentedAndGroups( pprocess ) );
		_output.append( paintOutput( pprocess, image ) );

		// incrememnt timer
		timestamp += 50; // 50ms = 20fps

		// update progress bar
		indicateIteration( pprocess->processed_frames );
	}

	populateObjectList( pprocess );

	free_process( pprocess );

	endLengthyProcess();

	on_positionSlider_sliderMoved( 0 );
}

// ============================================================
/// Populates object list
void GuiDriverWindow::populateObjectList( pprocess_t pprocess )
{
	assert( pprocess );

	// clear list
	listObjects->clear();
	_objects.clear();
	
	for ( int i =0 ; i < pprocess->object_number; i++ )
	{
		object_t& object = pprocess->objects[i];
		listObjects->addItem( QString("object %1 (%2 m/s)").arg( i ).arg( object.speed ) );
		_objects.append( object );
	}
}

// ============================================================
/// Updates data display on slider position change
///@param pos slider position
void GuiDriverWindow::on_positionSlider_sliderMoved( int pos )
{
	if ( pos == 0 ) return;
	
	// update image
	if ( _sequence.size() > pos ) {
		imageLabel->setPixmap( QPixmap::fromImage( _sequence.at( pos - 1 ) ) );
	}
	
	// update background
	if ( _backgrounds.size() > pos ) {
		backgroundLabel->setPixmap(  QPixmap::fromImage( _backgrounds.at( pos - 1 ) ) );
	}
	
	// update segmented
	if ( _segmented.size() > pos ) {
		segmentedLabel->setPixmap(  QPixmap::fromImage( _segmented.at( pos - 1 ) ) );
	}

	// update output
	if ( _output.size() > pos ) {
		outputLabel->setPixmap(  QPixmap::fromImage( _output.at( pos - 1 ) ) );
	}
	
	// update info
	labelFrame->setText( QString(tr("Frame %1 of %2")).arg( pos ).arg(_sequence.size()) );
}

// ============================================================
/// Confogures and displayts progress bar for lengthy process
///@param name			disp;layed process name 
///@param iterations	iterations the proces will have
void GuiDriverWindow::startLengthyProcess( const QString name, int iterations )
{
	progressBar->setMinimum( 1 );
	progressBar->setMaximum( iterations );
	//progressBar->setText( name );
	statusBar()->showMessage( name );

	progressBar->show();
	QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
}

// ============================================================
/// Idicates iteration of a lengthy process. Call only betweeen startProcess()
/// and endProcess();
///@param iteration current iteration
void GuiDriverWindow::indicateIteration( int iteration )
{
	progressBar->setValue( iteration );
	QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
}

// ============================================================
/// ends lengthy process started with startLengthyProcess();
void GuiDriverWindow::endLengthyProcess()
{
	progressBar->hide();
	statusBar()->showMessage( tr("Done") );

}

// ============================================================
/// Converts segmented image into QImage, and paints groups on it
QImage GuiDriverWindow::paintSegmentedAndGroups( pprocess_t pprocess )
{
	assert( pprocess );

	// skip first frame
	if ( pprocess->processed_frames == 1 ) {
		return QImage();
	}
	

	// prepare basaic colors, when needed
	if ( ! _basicColors.count() )
	{
		_basicColors << Qt::darkRed;
		_basicColors << Qt::darkGreen;
		_basicColors << Qt::darkCyan;
		_basicColors << Qt::darkYellow;
		_basicColors << Qt::darkMagenta;
	} 

	// cache some data
	int factor = 1 << pprocess->params.segmentation_scale_log; // size factor
	
	// get segmented image
	QImage segmented = imageToQt( pprocess->psegmented );

	// create larger version and its painter
	QImage image
		( segmented.width() * factor 
		, segmented.height() * factor
		, QImage::Format_RGB32
		);
	QPainter painter( &image );
		
	// copy smaller to larger
	painter.drawImage( QRect( QPoint(0,0), image.size() ), segmented );

	// paint groups
	for ( int i = 0; i < pprocess->group_number; i++ )
	{
		group_t& group = pprocess->groups[i];
		
		// paint group frame
		if ( group.pixels > 0 ) {
			painter.setPen( QPen( Qt::green, 2 ) );
		} else {
			painter.setPen( Qt::red );
		}

		QPainterPath framePath;
		framePath.moveTo( group.minx * factor, group.miny * factor );
		framePath.lineTo( (group.maxx+1) * factor, group.miny * factor );
		framePath.lineTo( (group.maxx+1) * factor, (group.maxy+1) * factor );
		framePath.lineTo( group.minx * factor, (group.maxy+1) * factor );
		framePath.lineTo( group.minx * factor, group.miny * factor );

		painter.drawPath( framePath );

		// paint group center
		// TODO uncomment when group centyer is calculated
		/* 
		painter.setPen( Qt::green );
		painter.moveTo( group.mx, group.my - 10 );
		painter.lineTo( group.mx, group.my + 10 );
		painter.moveTo( group.mx - 10, group.my );
		painter.lineTo( group.mx + 10, group.my );
		*/
	}

	// paint group pixels
	int* pgroup = pprocess->pgroupmap->pixels;
	for( int y = 0; y < segmented.height(); y++ ) {
		for( int x = 0; x < segmented.width() ; x++ ) {
			if ( *pgroup >= 0 )
			{
				int colorIndex = *pgroup % _basicColors.count();
				QColor color = _basicColors[ colorIndex ];

				painter.setPen( color );
				//painter.setPen( Qt::darkGreen ); // TODO trest, remove
				painter.drawEllipse( x * factor + 1, y* factor + 1, factor - 2, factor - 2 );
				
			}
			// take next pixel
			pgroup++;
		}
	}

	// convert to 8 bit
	return image.convertToFormat( QImage::Format_Indexed8, Qt::ThresholdDither );
}

// ============================================================
/// Paints output data on input image
QImage GuiDriverWindow::paintOutput( pprocess_t pprocess, const QImage& input )
{
	assert( pprocess );

	// skip first frame
	if ( pprocess->processed_frames == 1 ) {
		return input;
	}
	
	// create 32 bit version and its painter
	QImage image = input.convertToFormat( QImage::Format_RGB32 );
	QPainter painter( &image );
		
	// cache some data
	int factor = 1 << pprocess->params.segmentation_scale_log; // size factor
	
	// paint groups
	for ( int i = 0; i < pprocess->group_number; i++ )
	{
		group_t& group = pprocess->groups[i];
		
		// paint group frame
		if ( group.pixels > 0 ) {
			painter.setPen( QPen( Qt::green, 2 ) );

			QPainterPath framePath;
			framePath.moveTo( (int)(group.minx * factor), (int)(group.miny * factor) );
			framePath.lineTo( (int)((group.maxx+1) * factor), (int)(group.miny * factor) );
			framePath.lineTo( (int)((group.maxx+1) * factor), (int)((group.maxy+1) * factor) );
			framePath.lineTo( (int)(group.minx * factor), (int)((group.maxy+1) * factor) );
			framePath.lineTo( (int)(group.minx * factor), (int)(group.miny * factor) );

			painter.drawPath( framePath );
		}
		// paint group center
		if ( group.mx > 0 && group.my > 0 )
		{
			painter.setPen( Qt::green );

			painter.drawLine( (int)( group.mx * factor), (int)( group.my * factor - 10 ),
				(int)( group.mx * factor), (int)( group.my * factor + 10 ) );
			
			painter.drawLine( (int)( group.mx * factor - 10 ), (int)( group.my * factor),
				(int)(group.mx * factor + 10), (int)( group.my * factor ) );
		}

	}

	// overlay markers
	if ( ! _markersImage.isNull() )
	{
		painter.drawImage( 0, 0, _markersImage );
	}

	// convert to 8 bit
	return image.convertToFormat( QImage::Format_Indexed8, Qt::ThresholdDither );
}

// ============================================================
/// object list activation handle
void GuiDriverWindow::on_listObjects_itemActivated( QListWidgetItem* pitem )
{
	Q_ASSERT( pitem );
	
	int index = listObjects->row( pitem );

	tabWidget->setCurrentIndex( 3 );
	showObject( _objects[index] );
}

// ============================================================
/// Paints object's points on output frame
void GuiDriverWindow::showObject( const object_t& object )
{
	const QPixmap* labelPixmap = outputLabel->pixmap();;
	if ( ! labelPixmap )
		return;
		
	QPixmap pixmap = *labelPixmap;
	QPainter painter( &pixmap );


	for( int i = 0; i < object.point_number ; i++ )
	{
		// get coords
		int x = object.points[i].x;
		int y = object.points[i].y;
		// adjust color to marker
		if ( object.points[i].marker == 1 )
		{
			painter.setPen( Qt::yellow );
		}
		else if ( object.points[i].marker == 2 )
		{
			painter.setPen( Qt::blue );
		}
		else
		{
			painter.setPen( Qt::red );
		}
		// paint
		painter.drawLine( x, y - 10, x, y + 10 );
		painter.drawLine( x - 10, y, x + 10, y );
	}

	painter.end();
	
	outputLabel->setPixmap( pixmap );
}

// eof

